package com.easyim.imserver.server;

import com.alibaba.fastjson2.JSONObject;
import com.easyim.common.message.Immessage;
import com.easyim.imserver.config.TcpServerConfig;
import com.easyim.imserver.constant.RedisKey;
import com.easyim.imserver.handler.DispatchMessageHandler;
import com.easyim.imserver.utils.NodeInfoUtils;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class TcpServer {

    @Autowired
    private TcpServerConfig tcpServerConfig;

    @Autowired
    private DispatchMessageHandler dispatchMessageHandler;

    @Autowired
    @Qualifier("msgRedisTemplate")
    private RedisTemplate<String, String> redisTemplate;

    @PostConstruct
    public void init() {
        //把自己的ip地址以及tcpServer 写入服务列表 如果ip变了 肯定得重启
        //记录自己的nodeId 这个nodeId可以到处获取出来
        String localNodeIp = NodeInfoUtils.getLocalNodeIp();
        String ipPort = localNodeIp + ":" + tcpServerConfig.getPort();
        redisTemplate.opsForSet()
                .add(RedisKey.TCP_SERVER_ADDRESS, ipPort);

        Thread nettyThread = new Thread(() -> {
            log.info("start init tcpServer with args:{}", JSONObject.toJSONString(tcpServerConfig));
            EventLoopGroup bossGroup = getActiveEventLoopGroup(tcpServerConfig.getAcceptCount());
            EventLoopGroup workerGroup = getActiveEventLoopGroup(tcpServerConfig.getDealCount());
            try {
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.group(bossGroup, workerGroup)
                        .channel(getServerSocketChannelType())
                        .childHandler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) throws Exception {
                                ch.pipeline().addLast(new ProtobufVarint32FrameDecoder());
                                ch.pipeline().addLast(new ProtobufDecoder(
                                        Immessage.ImMessage.getDefaultInstance()));
                                ch.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
                                ch.pipeline().addLast(new ProtobufEncoder());
                                ch.pipeline().addLast(dispatchMessageHandler);
                            }
                        })
                        //tcp建链时候。需要完成三次握手。在三次握手中/三次完成后队列中(但是没被用户程序取走的) 这个参数和三次建链完成的队列大小相关
                        .childOption(ChannelOption.SO_BACKLOG, 1024)
                        //禁用Nagle。就是不等缓冲区凑够固定大小or到时间发送
                        .childOption(ChannelOption.TCP_NODELAY, false);

                ChannelFuture channelFuture =
                        serverBootstrap.bind(tcpServerConfig.getPort()).sync();

                //服务在这里阻塞住 所以一旦不阻塞了 关闭时候 会执行finally代码块
                channelFuture.channel().closeFuture().sync();
            } catch (Exception e) {
                log.info("create tcpServer error", e);
                throw new RuntimeException();
            } finally {
                //清理自己的
                redisTemplate.opsForSet().remove(RedisKey.TCP_SERVER_ADDRESS, ipPort);
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        });

        nettyThread.start();
    }


    private EventLoopGroup getActiveEventLoopGroup(int threadNum){
        //判断epoll是可用的
        if(Epoll.isAvailable()){
            return new EpollEventLoopGroup();
        }else {
            return new NioEventLoopGroup(threadNum);
        }
    }


    private Class<? extends ServerSocketChannel> getServerSocketChannelType(){
        if(Epoll.isAvailable()){
            return EpollServerSocketChannel.class;
        }else {
            return NioServerSocketChannel.class;
        }

    }
}
